package performance;

import struct.enums.CtxValidator;
import service.ctx.ctxChecker.CheckerStarter;
import service.ctx.ctxChecker.context.Context;
import service.ctx.ctxChecker.context.ContextChange;
import service.ctx.ctxServer.AbstractCtxServer;
import service.ctx.pattern.PatternManager;
import service.ctx.rule.RuleManager;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;

public class PTCtxServer extends AbstractCtxServer {

    private final String patternFilePath;
    private final String ruleFilePath;
    private final String bfuncClassFilePath;
    private final CtxValidator ctxValidator;
    private final String dataFilePath;

    private final HashMap<String, Context> contextHashMap;

    public PTCtxServer(
            String patternFilePath, String ruleFilePath,
            String bfuncJavaFilePath, CtxValidator ctxValidator,
            String dataFilePath) {
        this.patternFilePath = patternFilePath;
        this.ruleFilePath = ruleFilePath;
        this.ctxValidator = ctxValidator;
        this.dataFilePath = dataFilePath;

        this.contextHashMap = new HashMap<>();

        try {
            this.bfuncClassFilePath = compileBfuncsJavaFile(bfuncJavaFilePath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String compileBfuncsJavaFile(String bfuncJavaFilePath) throws IOException {
        File bfuncJavaFile = new File(bfuncJavaFilePath);
        if (!bfuncJavaFile.exists()) {
            System.err.println("Bfunc Java file does not exist: " + bfuncJavaFilePath);
            System.exit(1);
        }

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            System.err.println("No Java Compiler available");
            System.exit(1);
        }

        String outputDir = bfuncJavaFile.getParent();
        List<String> options = Arrays.asList("-d", outputDir);

        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(List.of(bfuncJavaFile));
        JavaCompiler.CompilationTask task = compiler.getTask(
                null, fileManager, null, options, null, compilationUnits
        );
        boolean success = task.call();
        fileManager.close();
        if (!success) {
            System.err.println("Bfunc Java compilation failed");
            System.exit(1);
        }
        return bfuncJavaFile.getAbsolutePath().replace(".java", ".class");
    }

    @Override
    public void init() {
        this.patternManager = new PatternManager(this);
        this.patternManager.buildPatterns(this.patternFilePath, null);
        this.ruleManager = new RuleManager(this, null);
        this.ruleManager.buildRules(this.ruleFilePath, null);
        this.checker = new CheckerStarter(this, this.bfuncClassFilePath, this.ctxValidator.toString());
    }

    public void runCheck() {
       try(InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(this.dataFilePath), StandardCharsets.UTF_8);
           BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
               ) {
           String line;
           while((line = bufferedReader.readLine()) != null) {
               ContextChange contextChange = buildContextChange(line.strip());
               this.checker.check(contextChange);
           }
       } catch (IOException e) {
           throw new RuntimeException(e);
       }
       this.checker.checkEnds();
    }

    private ContextChange buildContextChange(String changeString) {
        StringTokenizer stringTokenizer = new StringTokenizer(changeString, ",");
        ContextChange.ChangeType changeType = ContextChange.ChangeType.valueOf(stringTokenizer.nextToken());
        String pattern_id = stringTokenizer.nextToken();
        String ctx_id = stringTokenizer.nextToken();
        String timestamp = stringTokenizer.nextToken();
        String subject = stringTokenizer.nextToken();
        StringTokenizer st = new StringTokenizer(stringTokenizer.nextToken(), "_");
        String longitude = st.nextToken();
        String latitude = st.nextToken();
        String speed = st.nextToken();

        if (this.contextHashMap.containsKey(ctx_id)) {
            ContextChange change = new ContextChange();
            change.setChangeType(changeType);
            change.setPatternId(pattern_id);
            change.setContext(this.contextHashMap.get(ctx_id));
            return change;
        }
        else{
            Context context = new Context();
            context.setContextId(ctx_id);
            context.getContextFields().put("timestamp", timestamp);
            context.getContextFields().put("subject", subject);
            context.getContextFields().put("longitude", longitude);
            context.getContextFields().put("latitude", latitude);
            context.getContextFields().put("speed", speed);
            this.contextHashMap.put(ctx_id, context);
            ContextChange change = new ContextChange();
            change.setChangeType(changeType);
            change.setPatternId(pattern_id);
            change.setContext(context);
            return change;
        }
    }

    @Override
    public void run() {}

    @Override
    public void onMessage(String channel, String msg) {}
}
